/*NOTICE

This software was produced for the U. S. Government
under Contract No. FA8721-09-C-0002, and is
subject to the Rights in Noncommercial Computer Software
and Noncommercial Computer Software Documentation Clause
(DFARS) 252.227-7014 (JUN 1995)

(c) 2009 The MITRE Corporation. All Rights Reserved.
*/
package org.mitre.xmltransform.uml21tospmif;

import java.io.File;

import org.mitre.xmltransform.spmif.ComboStep;
import org.mitre.xmltransform.spmif.Device;
import org.mitre.xmltransform.spmif.ExecutionStep;
import org.mitre.xmltransform.spmif.ExternalReferenceStep;
import org.mitre.xmltransform.spmif.HierarchyStep;
import org.mitre.xmltransform.spmif.SpmifOutputDocument;
import org.mitre.xmltransform.spmif.StepState;
import org.mitre.xmltransform.transform.Transform;
import org.mitre.xmltransform.uml21.Uml21InputDocument;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class Uml21ToSpmifTransform extends Transform {

	@Override
	public void initialize(File pInputFile, File pOutputFile) {
		mInput = new Uml21InputDocument(pInputFile);
		mOutput = new SpmifOutputDocument(pOutputFile);
	}
	
	public void transform() {
		if (generateOutput()) {
			mOutput.finishDocument();
			mOutput.writeOutput();
		}		
	}

	private boolean generateOutput() {
		try {
			mOutput.createProjectElement(mInput.getInputValue("/xmi:XMI/uml:Model/@name"));
			NodeList interactions = mInput.getInputNodes("//packagedElement[@xmi:type='uml:Interaction']");
			//NodeList interactions = mInput.getInputNodes("//uml:Interaction[@xmi.id != '']");
			if (interactions == null) return true;
			Node n;
			for (int i = 0; i < interactions.getLength(); i++) {
				n = interactions.item(i);
				generatePerformanceScenariosForInteraction(n);
			}
			
			return true;
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}
		
	private Device getLocation(Node pContext) {
		//String executeOnLifelineId = mInput.getInputValue(node, "uml:InteractionFragment.covered/uml:Lifeline/@xmi.idref");
		//System.out.println("Execute on lifeline id = " + executeOnLifelineId);
		mInput.setVariable("lifelineId", mInput.getInputValue(pContext, "@covered"));
		mInput.setVariable("lifelinePropertyId", mInput.getInputValue(pContext, "ancestor::lifeline[@xmi:id=$lifelineId]/@represents"));
		mInput.setVariable("classId", mInput.getInputValue(pContext, "ancestor::ownedAttribute[@xmi:id=$lifelinePropertyId]/@type"));
		mInput.setVariable("lifelineInstanceName", mInput.getInputValue(pContext, "ancestor::lifeline[@xmi:id=$lifelineId]/@name"));
		//mInput.setVariable("dependencyId", mInput.getInputValue(pContext, "ancestor::uml:Namespace.ownedElement/uml:InstanceSpecification[@name=$lifelineInstanceName]/uml:ModelElement.clientDependency/uml:Dependency/@xmi.idref"));
		//mInput.setVariable("nodeId", mInput.getInputValue(pContext, "ancestor::uml:Namespace.ownedElement/uml:Dependency[@xmi.id=$dependencyId]/uml:Dependency.supplier/uml:Node/@xmi.idref"));
		//String nodeName = mInput.getInputValue(pContext, "ancestor::uml:Namespace.ownedElement/uml:Node[@xmi.id=$nodeId]/@name");
		//return nodeName;
		return null;
	}
	
	private void generatePerformanceScenariosForInteraction(Node pInteraction) {
		
		//PerfScenarioState state = new PerfScenarioState(name, pProjectElement, null);
		String name = mInput.getInputValue(pInteraction, "@name");
		NodeList nodes = mInput.getInputNodes(pInteraction, "fragment");
		
		Element perfScenarioElement = mOutput.createPerformanceScenario(name, mInput.getFileName(), null, null, null);
		Element executionGraphElement = mOutput.createExecutionGraph(perfScenarioElement, null, name, true);
		StepState state = new StepState(perfScenarioElement, executionGraphElement, name, null, null);
		
		generateExecutionGraph(	nodes, 0, state);
	}

	protected String buildEventOccurrenceNodeName(Node pEventOccurrence) {
		StringBuffer result = new StringBuffer();

		//mInput.setVariable("operationId", mInput.getInputValue(pEventOccurrence, "ancestor::uml:Interaction/uml:Interaction.message/uml:Message[@xmi.id=$messageId]/uml:Message.signature/uml:Operation/@xmi.idref"));
		mInput.setVariable("fragId", mInput.getInputValue(pEventOccurrence, "@xmi:id"));
		mInput.setVariable("modelId", mInput.getInputValue("/xmi:XMI/uml:Model/@xmi.id"));
		//mInput.setVariable("messageId", mInput.getInputValue(pEventOccurrence, "uml:MessageEnd.receiveMessage/uml:Message/@xmi.idref"));
		//String messageName = mInput.getInputValue(pEventOccurrence, "ancestor::uml:Interaction/uml:Interaction.message/uml:Message[@xmi.id=$messageId]/@name");
		String messageName = mInput.getInputValue(pEventOccurrence, "parent::node()/message[@receiveEvent=$fragId]/@name");
		//mInput.setVariable("lifelineId", mInput.getInputValue(pEventOccurrence, "uml:InteractionFragment.covered/uml:Lifeline/@xmi.idref"));
		mInput.setVariable("lifelineId", mInput.getInputValue(pEventOccurrence, "@covered"));
		//String lifelineInstanceName = mInput.getInputValue(pEventOccurrence, "ancestor::uml:Interaction/uml:Interaction.lifeline/uml:Lifeline[@xmi.id=$lifelineId]/@name");
		String lifelineInstanceName = mInput.getInputValue(pEventOccurrence, "parent::node()/lifeline[@xmi:id=$lifelineId]/@name");
		mInput.setVariable("lifelinePropId", mInput.getInputValue(pEventOccurrence, "parent::node()/lifeline[@xmi:id=$lifelineId]/@represents"));
		mInput.setVariable("lifelineClassId", mInput.getInputValue(pEventOccurrence, "parent::node()/ownedAttribute[@xmi:id=$lifelinePropId]/@type"));
		String lifelineClassName = mInput.getInputValue(pEventOccurrence, "ancestor::uml:Model/descendant::packagedElement[@xmi:type='uml:Class'][@xmi:id=$lifelineClassId]/@name");
		String name = lifelineClassName;
		if (lifelineClassName.length() == 0) {
			name = lifelineInstanceName;
		}
		if (messageName.length() == 0) {
			if (mInput.evaluateBooleanInputExpression(pEventOccurrence, "ancestor::uml:Interaction/uml:Interaction.message/uml:Message[@xmi.id=$messageId]/uml:ModelElement.stereotype")) {
				mInput.setVariable("stereotypeId", mInput.getInputValue(pEventOccurrence, "ancestor::uml:Interaction/uml:Interaction.message/uml:Message[@xmi.id=$messageId]/uml:ModelElement.stereotype/uml:Stereotype/@xmi.idref"));
				String stereotypeName = mInput.getInputValue(pEventOccurrence, "/XMI/XMI.content/uml:Model[@xmi.id=$modelId]/uml:Namespace.ownedElement/uml:Stereotype[@xmi.id=$stereotypeId]/@name");
				result.append(stereotypeName);
			} else {
				result.append("anon");
			}
			result.append("_");
			result.append(name);
		} else {
			result.append(messageName);
			NodeList messages = mInput.getInputNodes(pEventOccurrence, "ancestor::uml:Interaction/uml:Interaction.message/uml:Message[@xmi.id=$messageId]/uml:Message.argument/uml:OpaqueExpression[string-length(@body) > 0]");
			Node n;
			for (int i = 0; i < messages.getLength(); i++) {
				n = messages.item(i);
				result.append("_");
				result.append(mInput.getInputValue(n, "@body"));
			}
			result.append("_");
			result.append(name);
		}
		//let's put a the node number of the uml:EventOccurrence within the context of the entire XMI 
		result.append("_");
		NodeList eventOccurrences = mInput.getInputNodes("//uml:EventOccurrence");
		Node eo;
		String xmiId;
		String nodeId = mInput.getInputValue(pEventOccurrence, "@xmi.id");
		for (int i = 0; i < eventOccurrences.getLength(); i++) {
			eo = eventOccurrences.item(i);
			xmiId = mInput.getInputValue(eo, "@xmi.id");
			if (xmiId.equals(nodeId)) {
				result.append((i+1));
				break;
			}
		}
//		mInput.resetVariables();
		return result.toString();
	}

	private int generateExecutionGraph(NodeList pFragments, int pStartIndex, StepState pState) {
		Element newExecutionGraphElement, newPerformanceScenarioElement;
		//Element execGraphElem = createExecutionGraph(pState.getPerfScenarioElement(), pName, pIsMain);

		boolean startDone = false;
		// build hierarchy
		HierarchyStep newStep;
		NodeList combinedFragmentInteractionOperands;
		//NodeList fragmentList = mInput.getInputNodes(pContext, prefix + elementName + ".fragment/*");
		Node node, interactionOperand;
		String newExecutionGraphName;
		String id, execId, startId;
		Device startExecutionLocation;
		int endIndex = pFragments.getLength();
		
		for (int i = pStartIndex; i < pFragments.getLength(); i++) {
			node = pFragments.item(i);
			id = mInput.getInputValue(node, "@xmi:id");
			System.out.println("Fragment " + id);
			newStep = null;
			startDone = false;
			if (mInput.evaluateBooleanInputExpression(node, "boolean(self::fragment[@xmi:type='uml:MessageOccurrenceSpecification'])")) {
				startExecutionLocation = getLocation(node);
				System.out.println("Current location= " + pState.getLocation() + ". Node name = " + startExecutionLocation);
				if (mInput.evaluateBooleanInputExpression(node, "boolean(parent::node()/fragment[@xmi:type='uml:BehaviorExecutionSpecification'][@finish='" + id + "'])")) {
					execId = mInput.getInputValue(node, "parent::node()/fragment[@xmi:type='uml:BehaviorExecutionSpecification'][@finish='" + id + "']/@xmi:id");
					startId = mInput.getInputValue(node, "parent::node()/fragment[@xmi:type='uml:BehaviorExecutionSpecification'][@finish='" + id + "']/@start");
					if (startId.equals(id)) {
						System.out.println("reflexive call");
						newStep = new ExecutionStep(id, execId, node, buildEventOccurrenceNodeName(node), mInput, mOutput);
						System.out.println("      starting " + execId);
						pState.addParentStep(newStep);
						startDone = true;
					}
					System.out.println("    FINISH " + execId + " with " + id);
					if (!pState.closeParentStep(execId)) {
						//System.exit(0);
					}
					if (!pState.isSameLocation(startExecutionLocation)) {
						System.out.println("    end location");
						endIndex = i;
						break;
					}
				}
				if (startDone) {
					startDone = false;
				} else if (mInput.evaluateBooleanInputExpression(node, "boolean(parent::node()/fragment[@xmi:type='uml:BehaviorExecutionSpecification'][@start='" + id + "'])")) {
					System.out.println("    START " + id);
					if (!pState.isSameLocation(startExecutionLocation)) {
						System.out.println("    create new");
						newStep = new ExternalReferenceStep(id, null, node, mInput, mOutput);
						pState.addChildStep(newStep);
						String psName = pState.getName() + "_" + startExecutionLocation;
						((ExternalReferenceStep)newStep).setPSName(psName, mOutput.getOutputFileName());
						
						newPerformanceScenarioElement = mOutput.createPerformanceScenario(psName, mInput.getFileName(), null, null, null);
						newExecutionGraphElement = mOutput.createExecutionGraph(newPerformanceScenarioElement, null, psName, true); 
						i = generateExecutionGraph(	pFragments,
												i, 
												new StepState(newPerformanceScenarioElement, newExecutionGraphElement, psName, pState, startExecutionLocation));
						/*newExecutionGraphElement = createExecutionGraph(pState.getPerformanceScenarioElement(), egName, true);
						i = generateExecutionGraph(	pFragments,
												i, 
												new StepState(pState.getPerformanceScenarioElement(), newExecutionGraphElement, egName, pState, startExecutionLocation));*/
					} else {
						execId = mInput.getInputValue(node, "parent::node()/fragment[@xmi:type='uml:BehaviorExecutionSpecification'][@start='" + id + "']/@xmi:id");
						newStep = new ExecutionStep(id, execId, node, buildEventOccurrenceNodeName(node), mInput, mOutput);
						System.out.println("      starting " + execId);
						pState.addParentStep(newStep);
					}
				}
			} else if (mInput.evaluateBooleanInputExpression(node, "boolean(self::uml:CombinedFragment)")) {
				newStep = new ComboStep(id, null, node, buildEventOccurrenceNodeName(node), mInput, mOutput);
				pState.addChildStep(newStep);
				// more execution graphs?
				combinedFragmentInteractionOperands = mInput.getInputNodes(node, "uml:CombinedFragment.operand/uml:InteractionOperand");
				for (int j = 0; j < combinedFragmentInteractionOperands.getLength(); j++) {
					interactionOperand = combinedFragmentInteractionOperands.item(j);
					System.out.println("start fragment");
					newExecutionGraphName = mInput.buildNodeName(interactionOperand, null);
					newExecutionGraphElement = mOutput.createExecutionGraph(pState.getPerformanceScenarioElement(), null, newExecutionGraphName, false); 
					generateExecutionGraph(	mInput.getInputNodes(interactionOperand, "uml:InteractionOperand.fragment/*"),
											0, 
											new StepState(pState.getPerformanceScenarioElement(), newExecutionGraphElement, newExecutionGraphName, pState, pState.getLocation()));
					System.out.println("done fragment");
				}
			} else if (mInput.evaluateBooleanInputExpression(node, "boolean(self::uml:InteractionOccurrence)")) {
/*				newStep = new ReferenceStep(id, null, node, mInput, mOutput);
				pState.addChildStep(newStep);*/
				newStep = new ExternalReferenceStep(id, null, node, mInput, mOutput);
				pState.addChildStep(newStep);
			}
			pState.addToStepMap(id, newStep);
		}
		
		//if (pIsMain && !pState.isValidEndState()) {
		if (!pState.isValidEndState()) {
			throw new IllegalArgumentException("Failed to close all parent steps.");
		}
		
		// start node
		if (pState.getNumberOfRootSteps() > 0) {
			HierarchyStep startStep = pState.getRootStep(0);
			pState.getExecutionGraphElement().setAttribute("StartNode", startStep.getStartNodeName());
		} else {
			pState.getExecutionGraphElement().setAttribute("StartNode", "");
		}
				
		// nodes
		HierarchyStep step;
		for (int i = 0; i < pState.getNumberOfRootSteps(); i++) {
			step = pState.getRootStep(i);
			step.generateStep(pState.getExecutionGraphElement(), pState.getLocation());
		}
		
		// arcs
		String fromNode = null;
		for (int i = 0; i < pState.getNumberOfRootSteps(); i++) {
			step = pState.getRootStep(i);
			fromNode = step.generateArcs(pState.getExecutionGraphElement(), fromNode);
		}
		return endIndex;
	}
	
	private Uml21InputDocument mInput;
	private SpmifOutputDocument mOutput;
}
